package AnalizadorLexico;

import java.util.ArrayList;

/*
 * clase que realiza el analisis lexico del lenguaje
 * creado por nosotros llamado monky
 */
/**
 *
 * @author Juan Pablo Ramirez
 * @author Esteban Davila
 * @author Johan Sebastian Aguirre
 */
public class AnalizadorLexico implements CategoriaLexica {

    /**
     * declaracion de variables.
     */
    private int filaActual,  columnaActual,  posicion,  posBackTracking,  filaIni,  columnaIni;
    private String codigo;
    private char caracterActual;
    private ArrayList<Lexema> tablaSimbolos;
    private ArrayList<ErrorLexico> tablaErrores;
    private boolean terminaAnalisis;

    /**
     * Constructor del analizador lexico (Scanner) inicializa los apuntadores
     * para la posicion 0 y el codigo como ""
     */
    public AnalizadorLexico() {
        tablaSimbolos = new ArrayList<Lexema>();
        tablaErrores = new ArrayList<ErrorLexico>();
        filaActual = 1;
        columnaActual = 1;
        posicion = 0;
        posBackTracking = 0;
        filaIni = 1;
        columnaIni = 1;
        codigo = "";
        terminaAnalisis = false;
    }

    /**
     * Este metodo analiza todas las categorias lexicas y almacenar cada token
     * en la tabla de simbolos, y si encuentra un error lo reporta y se almacena
     * en la tabla de errores.
     */
    public void analizar(String codigo) {
        this.codigo = codigo;
        this.codigo += "  " + EOC;// agregamos el caracter de fin de codigo al codigo.

        caracterActual = codigo.charAt(0);
        while (!terminaAnalisis) {
            if (consumirCaracter()) {
                continue;
            }
            if (esComentario()) {
                continue;
            }
            if (esSeparador()) {
                continue;
            }
            if (esCaracter()) {
                continue;
            }
            if (esCadena()) {
                continue;
            }
            if (esReal()) {
                continue;
            }
            if (esEntero()) {
                continue;
            }
            if (esIdentificadorDeClase()) {
                continue;
            }
            if (esIdentificador()) {
                continue;
            }
            if (esOperadorIncremento()) {
                continue;
            }
            if (esOperadorDecremento()) {
                continue;
            }
            if (esOperadorRelacional()) {
                continue;
            }
            if (esAsignacion()) {
                continue;
            }
            if (esOperadorAritmetico()) {
                continue;
            }
            if (esOperadorLogico()) {
                continue;
            }
            if (esAgrupacion()) {
                continue;
            }
            if (esEOC()) {
                terminaAnalisis = true;
                break;
            }
            // reportar error
            guardarError("" + caracterActual, "caracter desconocido");
            siguienteCaracter();
        // break;
        }
    }

    // METODOS DE IDENTIFICACION DE TOKENS
    /**
     * metodo que sirve para decir si el token evaluado es un identificador de
     * clase
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esIdentificadorDeClase() {
        // si no empieza en mayuscula rechazo inmediato
        if (!(Character.isUpperCase(caracterActual))) {
            return false;
        } else {
            String token = "";
            filaIni = filaActual;
            columnaIni = columnaActual;
            token += caracterActual;
            siguienteCaracter();
            // mientras sea digito o letra
            while (Character.isLetterOrDigit(caracterActual)) {
                token += caracterActual;
                siguienteCaracter();
            }
            guardarToken(token, IDCLASE);
            return true;
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es un operador de
     * incremento
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esOperadorIncremento() {
        String token = "";
        posBackTracking = posicion;
        filaIni = filaActual;
        columnaIni = columnaActual;
        if (!(caracterActual == '+')) {
            return false;
        } else {
            token += caracterActual;
            siguienteCaracter();
            // mientras sea letra o digito
            if (caracterActual == '+') {
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, OPERADORINCREMENTO);
                return true;
            } else {
                backTracking();
                return false;
            }
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es un operador de
     * decremento
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esOperadorDecremento() {
        posBackTracking = posicion;
        String token = "";
        filaIni = filaActual;
        columnaIni = columnaActual;
        if (!(caracterActual == '-')) {
            return false;
        } else {
            token += caracterActual;
            siguienteCaracter();
            // mientras sea letra o digito
            if (caracterActual == '-') {
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, OPERADORDECREMENTO);
                return true;
            } else {
                backTracking();
                return false;
            }
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es un identificador
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esIdentificador() {
        String token = "";
        filaIni = filaActual;
        columnaIni = columnaActual;
        // si no empieza por letra
        if (!(Character.isLetter(caracterActual))) {
            return false;
        } else {
            token += caracterActual;
            siguienteCaracter();
            // mientras sea letra o digito
            while (Character.isLetterOrDigit(caracterActual)) {
                token += caracterActual;
                siguienteCaracter();
            }
            guardarToken(token, IDENTIFICADOR);
            return true;
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es un separador ,.:;
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esSeparador() {
        String token = "";
        filaIni = filaActual;
        columnaIni = columnaActual;
        switch (caracterActual) {
            case '.':
            case ',':
            case ':':
            case ';':
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, SEPARADOR);
                return true;
        }
        return false;
    }

    /**
     * metodo que sirve para decir si el token evaluado es un operador
     * aritmetico
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esOperadorAritmetico() {
        String token = "";
        filaIni = filaActual;
        columnaIni = columnaActual;
        // si no es alguno de los simbolos rechazo
        if (!(caracterActual == '+' || caracterActual == '-' || caracterActual == '*' || caracterActual == '/' || caracterActual == '%' || caracterActual == '^')) {
            return false;
        } else {
            token += caracterActual;
            siguienteCaracter();
            guardarToken(token, OPARITMETICO);
            return true;
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es operador logico
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esOperadorLogico() {
        String token = "";
        filaIni = filaActual;
        columnaIni = columnaActual;
        // si no es alguno de los simbolos rechazo
        if (!(caracterActual == '&' || caracterActual == '|' || caracterActual == '!')) {
            return false;
        } else {
            token += caracterActual;
            siguienteCaracter();
            guardarToken(token, OPLOGICO);
            return true;
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es operador relacional
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esOperadorRelacional() {
        posBackTracking = posicion;
        columnaIni = columnaActual;
        filaIni = filaActual;
        String token = "";
        if (caracterActual == '<' || caracterActual == '>') {
            token += caracterActual;
            siguienteCaracter();
            if (caracterActual == '=') {
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, OPRELACIONAL);
                return true;
            } else {
                guardarToken(token, OPRELACIONAL);
                return true;
            }
        } else if (caracterActual == '=') {
            token += caracterActual;
            siguienteCaracter();
            if (caracterActual == '=') {
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, OPRELACIONAL);
                return true;
            } else {
                backTracking();
                return false;
            }

        } else if (caracterActual == '!') {
            token += caracterActual;
            siguienteCaracter();
            if (caracterActual == '=') {
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, OPRELACIONAL);
                return true;
            } else {
                backTracking();
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es un caracter
     *
     * @return true si se cumple false si no
     */
    private boolean esCaracter() {
        posBackTracking = posicion;
        columnaIni = columnaActual;
        filaIni = filaActual;
        String token = "";
        if (caracterActual == '\'') {
            token += caracterActual;
            siguienteCaracter();
            if (caracterActual == '\\') {
                token += caracterActual;
                siguienteCaracter();
            }
            token += caracterActual;
            siguienteCaracter();
            if (caracterActual == '\'') {
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, CARACTER);
                return true;
            } else {
                guardarError(token, "CaracterSinCerrar");
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es una cadena
     *
     * @return true si se cumple false si no
     */
    private boolean esCadena() {
        posBackTracking = posicion;
        columnaIni = columnaActual;
        filaIni = filaActual;
        String token = "";
        if (caracterActual == '"') {
            token += caracterActual;
            siguienteCaracter();
            while (true) {
                if (caracterActual == '\\') {
                    token += caracterActual;
                    siguienteCaracter();
                    token += caracterActual;
                    siguienteCaracter();
                }
                if (caracterActual == '\n' || caracterActual == EOC) {
                    token += caracterActual;
                    siguienteCaracter();
                    guardarError(token, "CadenaSinCerrar");
                    return false;
                }
                if (caracterActual == '"') {
                    token += caracterActual;
                    siguienteCaracter();
                    guardarToken(token, CADENA);
                    return true;
                }
                token += caracterActual;
                siguienteCaracter();
            }
        } else {
            return false;
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es un simbolo de
     * agrupacion
     *
     * @return true si se cumple false si no
     */
    private boolean esAgrupacion() {
        posBackTracking = posicion;
        columnaIni = columnaActual;
        filaIni = filaActual;
        String token = "";
        switch (caracterActual) {
            case '{':
            case '}':
            case '[':
            case ']':
            case '(':
            case ')':
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, AGRUPACION);
                return true;
        }
        return false;
    }

    /**
     * metodo que sirve para comprobar si el token actual es un numero entero
     *
     * @return boolean true si cumple false si no
     */
    private boolean esEntero() {
        posBackTracking = posicion;
        columnaIni = columnaActual;
        filaIni = filaActual;
        String token = "";
        if (Character.isDigit(caracterActual)) {
            token += caracterActual;
            siguienteCaracter();
            while (Character.isDigit(caracterActual)) {
                token += caracterActual;
                siguienteCaracter();
            }
            guardarToken(token, NUMENTERO);
            return true;
        } else {
            return false;
        }
    }

    /**
     * metodo que sirve para comprobar si el token actual es un numero real
     *
     * @return boolean true si cumple false si no
     */
    private boolean esReal() {
        posBackTracking = posicion;
        columnaIni = columnaActual;
        filaIni = filaActual;
        String token = "";
        // estado0
        if (Character.isDigit(caracterActual)) {
            token += caracterActual;
            siguienteCaracter();
            while (true) {
                // estado1
                if (Character.isDigit(caracterActual)) {
                    token += caracterActual;
                    siguienteCaracter();
                } else {
                    break;
                }
            }
            // estado1
            if (caracterActual == '.') {
                token += caracterActual;
                siguienteCaracter();
                while (true) {
                    // estado2
                    if (Character.isDigit(caracterActual)) {
                        token += caracterActual;
                        siguienteCaracter();
                    } else {
                        break;
                    }
                }
                // estado3
                guardarToken(token, NUMREAL);
                return true;
            } else {
                // backtracking estado7
                backTracking();
                return false;
            }
        // estado0
        } else if (caracterActual == '.') {
            token += caracterActual;
            siguienteCaracter();
            // estado4
            if (Character.isDigit(caracterActual)) {
                token += caracterActual;
                siguienteCaracter();
                while (true) {
                    // estado5
                    if (Character.isDigit(caracterActual)) {
                        token += caracterActual;
                        siguienteCaracter();
                    } else {
                        break;
                    }
                }
                // estado6
                guardarToken(token, NUMREAL);
                return true;
            } else {
                // backtracking estado 8
                backTracking();
                return false;
            }
        } else {
            // estado9
            return false;
        }
    }

    /**
     * metodo que sirve para comprobar si el token actual es una asignacion
     *
     * @return boolean true si cumple false si no
     */
    private boolean esAsignacion() {
        posBackTracking = posicion;
        filaIni = filaActual;
        columnaIni = columnaActual;
        String token = "";
        if (caracterActual == '+' || caracterActual == '-' || caracterActual == '*' || caracterActual == '/') {
            token += caracterActual;
            siguienteCaracter();
            if (caracterActual == '=') {
                token += caracterActual;
                siguienteCaracter();
                guardarToken(token, ASIGNACION);
                return true;
            } else {
                backTracking();
                return false;
            }
        } else if (caracterActual == '=') {
            token += caracterActual;
            siguienteCaracter();
            guardarToken(token, ASIGNACION);
            return true;
        } else {
            return false;
        }
    }

    /**
     * metodo que sirve para comprobar si el token actual es un comentario pero
     * no lo agrega a la lista
     *
     * @return boolean true si cumple false si no
     */
    private boolean esComentario() {
        String token = "";
        posBackTracking = posicion;
        filaIni = filaActual;
        columnaIni = columnaActual;
        // estado0
        if (!(caracterActual == '#')) {
            // rechazo inmediato
            return false;
        } else {
            token += caracterActual;
            siguienteCaracter();
            // estado1
            if ((caracterActual == '<')) {
                token += caracterActual;
                siguienteCaracter();
                // estado3
                if ((caracterActual == '<')) {
                    token += caracterActual;
                    siguienteCaracter();
                    // estado4
                    while (!esEOC()) {
                        if (caracterActual == '\n') {
                            columnaActual = 1;
                            filaActual++;
                        }
                        // estado4
                        if (caracterActual == '>') {
                            token += caracterActual;
                            siguienteCaracter();
                            // estado5
                            if (caracterActual == '>') {
                                token += caracterActual;
                                siguienteCaracter();
                                return true;
                            } else {
                                guardarError(token,
                                        "ComentarioDeVariasLineasSinCerrar");
                                return false;
                            }
                        }
                        token += caracterActual;
                        siguienteCaracter();
                    }
                    guardarError(token, "ComentarioDeVariasLineasSinCerrar");
                    return false;
                } else {
                    token += caracterActual;
                    return false;
                }
            } else {
                // estado 1
                while (true) {
                    if (caracterActual == '\n') {
                        // estado 2
                        return true;
                    }
                    token += caracterActual;
                    siguienteCaracter();
                }
            }
        }
    }

    /**
     * metodo que sirve para decir si el token evaluado es el fin del codigo
     *
     * @return boolean true si se cumple false si no
     */
    private boolean esEOC() {
        return caracterActual == EOC;
    }

    // METODOS DE SOPORTE
    /**
     * Guarda el token en la tabla de errores y coloca su respectivo token el
     * tipo de error y la posicion en la que se encuentra en el codigo
     *
     * @param lexema
     * @param error
     */
    private void guardarError(String lexema, String error) {
        ErrorLexico miError = new ErrorLexico(lexema, error, filaIni,
                columnaIni);
        tablaErrores.add(miError);
    }

    /**
     * Guarda el token en la tabla de simbolos y coloca su respectivo token el
     * tipo y la posicion en la que se encuentra en el codigo
     *
     * @param lexema
     * @param tipo
     */
    private void guardarToken(String lexema, String tipo) {
        Lexema miLexema = new Lexema(lexema, tipo, filaIni, columnaIni);
        tablaSimbolos.add(miLexema);
    }

    /**
     * metodo para consumir caracter que no se usa para el codigo
     *
     * @return boolean true si se cumple false si no
     */
    private boolean consumirCaracter() {
        // si no es espacio ni ni \t
        String token = "";
        filaIni = filaActual;
        columnaIni = columnaActual;
        if (caracterActual == '\n') {
            filaActual++;
            columnaActual = 0;
            token += caracterActual;
            siguienteCaracter();
            guardarToken(token, SEPARADORSENTENCIA);
            return true;
        }
        if (!(caracterActual == ' ' || caracterActual == '\t')) {
            return false;
        } else {
            if (caracterActual == '\t') {
                columnaActual += 7;
            }
            siguienteCaracter();
            return true;
        }
    }

    /**
     * Avanza un caracter en el codigo fuente si no se ha alcanzado el fin de
     * codigo en caso de que se alcance el fin de codigo termina el analisis
     */
    private void siguienteCaracter() {
        if (esEOC()) {
            terminaAnalisis = true;
            return;
        }
        posicion++;
        caracterActual = codigo.charAt(posicion);
        columnaActual++;
    }

    /**
     * devuelve los apuntadores del codigo a la posicion en que se encontraban
     * antes de verificar si correspondia a un lexema
     */
    private void backTracking() {
        posicion = posBackTracking - 1;
        columnaActual = columnaIni-1;
        filaActual = filaIni;
        siguienteCaracter();
    }

    /**
     * retorna la tabla de simbolos que contiene los tokens o lexemas
     * encontrados en el analisis
     *
     * @return ArrayList retorna la tabla de simbolos que contiene los tokens o
     *         lexemas encontrados en el analisis
     */
    public ArrayList<Lexema>  getTablaDeSimbolos() {
        return tablaSimbolos;
    }

    /**
     * retorna la tabla de simbolos que contiene los errores encontrados en el
     * analisis
     *
     * @return ArrayList retorna la tabla de simbolos que contiene los errores
     *         encontrados en el analisis
     */
    public ArrayList <ErrorLexico> getTablaDeErrores() {
        return tablaErrores;
    }
}